package com.christianposta.learnddd.domain.model.cargo;

import java.util.Date;

import org.apache.commons.lang.Validate;


import com.christianposta.learnddd.domain.model.handling.HandlingEvent;
import com.christianposta.learnddd.domain.model.handling.HandlingHistory;
import com.christianposta.learnddd.domain.model.handling.HandlingType;
import com.christianposta.learnddd.domain.model.location.Location;
import com.christianposta.learnddd.domain.model.voyage.Voyage;
import com.christianposta.learnddd.domain.shared.ValueObject;

/**
 * Actual delivery of a cargo as opposed to the customer requirements (RouteSpecification)
 * or planned delivery (Itinerary)
 * @author ceposta
 *
 */
public class Delivery implements ValueObject<Delivery> {

	
	private Date calcuatedAt;
	private Date eta;
	private HandlingEvent lastEvent;
	private boolean misdirected;
	private boolean isUnloadedAtDestination;
	private RoutingStatus routingStatus;
	private TransportStatus transportStatus;
	private Location lastKnownLocation;
	private Voyage currentVoyage;
	private HandlingActivity nextExpectedActivity;
	
	private static final Date ETA_UNKOWN = null;
	private static final HandlingActivity NO_ACTIVITY = null;

	/**
	 * Create a new object describing a delivery. Itinerary is allowed to be null, but route specification
	 * and handling history cannot be null (but handling history is allowed to be empty).
	 * 
	 * @param routeSpecification
	 * @param itinerary
	 * @param history
	 * @return
	 */
	static Delivery derivedFrom(RouteSpecification routeSpecification, Itinerary itinerary, HandlingHistory history){
		Validate.notNull(routeSpecification);
		Validate.notNull(history);
		
		final HandlingEvent lastEvent = history.mostRecentlyCompletedEvent();
		return new Delivery(lastEvent, itinerary, routeSpecification);
	}
	
	public boolean sameValueAs(Delivery other) {
		return false;
	}

	
	/**
	 * Internal constructor. 
	 * Delivery is a value object, and its creation is controlled by public factory methods on 
	 * the Delivery class.
	 * 
	 * @param lastEvent
	 * @param itinerary
	 * @param routeSpecification
	 */
	private Delivery(HandlingEvent lastEvent, Itinerary itinerary,
			RouteSpecification routeSpecification) {
		this.calcuatedAt = new Date();
		this.lastEvent = lastEvent;
		
		// TODO finish all calcuations and unit tests
		this.misdirected = calculateMisdirectionStatus(itinerary);
		this.routingStatus = calculateRoutingStatus(itinerary, routeSpecification);
		this.transportStatus = calculateTransportStatus();
		this.lastKnownLocation = calculateLastKnownLocation();
		this.currentVoyage = calculateCurrentVoyage();
		this.eta = calculateEta(itinerary);
		
		this.nextExpectedActivity = calculateNextExpectedActivity(routeSpecification, itinerary);
		this.isUnloadedAtDestination = calculateUnloadedAtDestination(routeSpecification);
		
		
	}

	public boolean isUnloadedAtDestination(){
		return this.isUnloadedAtDestination;
	}
	private boolean calculateUnloadedAtDestination(
			RouteSpecification routeSpecification) {
		return this.lastEvent != null 
				&& HandlingType.UNLOAD.sameValueAs(this.lastEvent.type()) 
				&& routeSpecification.destination().sameIdentityAs(this.lastEvent.location());
	}


	private HandlingActivity calculateNextExpectedActivity(
			RouteSpecification routeSpecification, Itinerary itinerary) {
		// TODO Auto-generated method stub
		return null;
	}

	
	public Date estimatedTimeOfArrival(){
		if(eta != ETA_UNKOWN){
			return new Date(eta.getTime());
		}
		else {
			return ETA_UNKOWN;
		}
			
	}
	private Date calculateEta(Itinerary itinerary) {
		if(onTrack()){
			return itinerary.finalArrivalDate();
		}
		else {
			return ETA_UNKOWN;
		}
	}

	private boolean onTrack() {
		// TODO finsih routing status logic && misdirected calculations
		// for the unit tests
		return routingStatus.equals(RoutingStatus.ROUTED) && !this.misdirected;
	}

	private Voyage calculateCurrentVoyage() {
		// TODO Auto-generated method stub
		return null;
	}

	private Location calculateLastKnownLocation() {
		// TODO Auto-generated method stub
		return null;
	}

	private TransportStatus calculateTransportStatus() {
		// TODO Auto-generated method stub
		return null;
	}

	private RoutingStatus calculateRoutingStatus(Itinerary itinerary,
			RouteSpecification routeSpecification) {
		// TODO Auto-generated method stub
		return null;
	}

	private boolean calculateMisdirectionStatus(Itinerary itinerary) {
		// TODO Auto-generated method stub
		return false;
	}
}
